﻿using System;
using System.IO;
using System.Text;

namespace BOF
{
    public enum LinesPercentage
    {
        Lines = 0,
        Percentage
    }

    public enum ReadType
    {
        Start = 0,
        Middle,
        End
    }

    public class PartialFileRead
    {
        #region Fields and Properties

        public string FileName { get; set; }

        public FileInfo FileInfo { get; private set; }

        public ReadType ReadType { get; set; }

        public long Lines { get; set; }

        public long ReadChars { get; private set; }

        public string ReadLines { get; private set; }

        public LinesPercentage LinesOrPercentage { get; set; }

        public bool DoTabs { get; set; }

        public long StartLine { get; set; }

        public long EndLine { get; set; }

        public string LineTerminator
        {
            get { return GetLineTerminator(); }
        }

        public string HeaderLine { get; private set; }

        public bool HasHeader { get; private set; }

        public string FooterLine { get; private set; }

        public bool HasFooter { get; private set; }

        #endregion

        #region Events

        public event ReadFileProgressChangedEventHandler ReadProgress;
        public event FileReadEventHandler FileRead;

        #endregion

        #region Constructor

        public PartialFileRead()
        {
            FileName = string.Empty;
            ReadType = ReadType.Start;
            Lines = 10;
            ReadChars = 0;
            ReadLines = string.Empty;
            LinesOrPercentage = LinesPercentage.Lines;
            DoTabs = false;
            StartLine = 0;
            EndLine = 0;
        }

        #endregion

        public string ReadFile()
        {
            //reset variables
            ReadChars = 0;
            ReadLines = string.Empty;
            HasFooter = false;
            FooterLine = string.Empty;
            HasHeader = false;
            HeaderLine = string.Empty;

            switch (ReadType)
            {
                case ReadType.Start:
                    ReadFileStart();
                    break;

                case ReadType.Middle:
                    ReadFileMiddle();
                    break;

                case ReadType.End:
                    ReadFileEnd();
                    break;

                default:
                    break;
            }

            CheckForHeaderAndFooter();

            OnFileRead();

            return ReadLines;
        }

        /// <summary>
        /// Crude guesstimate at the existence of a header or footer record.
        /// Based if line length less half of normal line, or if 2/3rds are non-numeric
        /// </summary>
        private void CheckForHeaderAndFooter()
        {
            //checks for header/footer in *read* text, not much use
            //string[] lines = ReadLines.Split('\n');

            //if (lines[0].Length < lines[1].Length / 2)
            //    HasHeader = true;
            //else
            //    HasHeader = false;

            ////there's an blank line at the end we need to ignore
            //if (lines[lines.Length - 2].Length < lines[lines.Length - 3].Length / 2)
            //    HasFooter = true;
            //else
            //    HasFooter = false;


            //header check, read first 2 lines
            using (StreamReader sr = FileInfo.OpenText())
            {
                string line1 = sr.ReadLine();
                string line2 = sr.ReadLine();

                if (line2 != null
                    && line1.Length < line2.Length / 2
                    || !(System.Text.RegularExpressions.Regex.Matches(line1, "[0-9]").Count > line1.Length / 3))
                {
                    HasHeader = true;
                    HeaderLine = line1;
                }
            }

            //footer check, read last 2 lines
            using (StreamReader sr = new StreamReader(FileInfo.FullName, true))
            {
                long len = sr.BaseStream.Length;
                int[] rb = new int[2];

                //skip trailing line terminators
                do
                {
                    len -= 2;
                    sr.BaseStream.Seek(len, SeekOrigin.Begin);
                    rb[1] = sr.BaseStream.ReadByte();
                    rb[0] = sr.BaseStream.ReadByte();
                } while (rb[0] == 10 && rb[1] == 13);

                //go back _lines lines
                int b = 0;
                for (int i = 0; i < 3 - 1; i++)
                {
                    do
                    {
                        len -= 1;
                        try
                        {
                            sr.BaseStream.Seek(len, SeekOrigin.Begin);
                            b = sr.BaseStream.ReadByte();
                        }
                        catch (IOException ex)
                        {
                            Console.WriteLine(ex.Message);

                            //more lines asked for than in file
                            //move to beginning
                            sr.BaseStream.Seek(0, SeekOrigin.Begin);
                        }
                    } while (b != 10 && len > 0);

                }

                string line1 = sr.ReadLine();
                string line2 = sr.ReadLine();

                if (line2.Length < line1.Length / 2 ||
                    !(System.Text.RegularExpressions.Regex.Matches(line1, "[0-9]").Count > line1.Length / 3))
                {
                    HasFooter = true;
                    FooterLine = line2;
                }
            }
        }

        private string GetLineTerminator()
        {
            FileInfo = new FileInfo(FileName);

            if (!FileInfo.Exists)
                throw new ApplicationException(FileInfo.Name + " not found!");

            byte[] b = new byte[2];

            //open file stream
            using (FileStream fs = FileInfo.Open(FileMode.Open, FileAccess.Read))
            {
                //read 2 bytes from end of file into b
                fs.Seek(-2, SeekOrigin.End);
                fs.Read(b, 0, 2);
            }

            return b[0].ToString() + ", " + b[1].ToString();
        }

        private void ReadFileStart()
        {
            FileInfo = new FileInfo(FileName);

            if (!FileInfo.Exists)
                throw new ApplicationException(FileInfo.Name + " not found!");

            long i = 0;
            string line;
            StringBuilder sb = new StringBuilder();

            using (StreamReader sr = FileInfo.OpenText())
            {
                switch (LinesOrPercentage)
                {
                    case LinesPercentage.Lines:
                        while ((i < Lines) && !sr.EndOfStream)
                        {
                            line = sr.ReadLine();
                            ReadChars += line.Length;

                            if (DoTabs)
                                line = line.Replace(',', '\t');

                            sb.AppendLine(line);

                            i++;
                            OnReadFileProgress(i, Lines);
                        }
                        break;

                    case LinesPercentage.Percentage:
                        double percent = FileInfo.Length * (Lines / 100);

                        while (ReadChars <= percent && !sr.EndOfStream)
                        {
                            line = sr.ReadLine();
                            ReadChars += line.Length;

                            if (DoTabs)
                                line = line.Replace(',', '\t');

                            sb.AppendLine(line);

                            i++;
                            OnReadFileProgress(ReadChars, (long) percent);
                        }
                        break;
                }
            }

            ReadLines = sb.ToString();
        }

        private void ReadFileMiddle()
        {
            FileInfo = new FileInfo(FileName);

            if (!FileInfo.Exists)
                throw new ApplicationException(FileInfo.Name + " not found!");

            if (StartLine <= 0)
                StartLine = 1;

            if (EndLine <= StartLine)
                EndLine = StartLine + 1;

            string line;
            long i = 0;
            StringBuilder sb = new StringBuilder();

            using (StreamReader sr = FileInfo.OpenText())
            {
                //skip the first lines
                while (i < (StartLine - 1) && !sr.EndOfStream)
                {
                    sr.ReadLine();
                    i++;
                    OnReadFileProgress(i, StartLine);
                }

                while (i < EndLine && !sr.EndOfStream)
                {
                    line = sr.ReadLine();

                    ReadChars += line.Length;

                    if (DoTabs)
                        line = line.Replace(',', '\t');

                    sb.AppendLine(line);

                    i++;
                    OnReadFileProgress(i, EndLine);
                }
            }

            ReadLines = sb.ToString();
        }

        private void ReadFileEnd()
        {
            FileInfo = new FileInfo(FileName);

            if (!FileInfo.Exists)
                throw new ApplicationException(FileInfo.Name + " not found!");

            StringBuilder sb = new StringBuilder();

            using (StreamReader sr = new StreamReader(FileInfo.FullName, true))
            {
                long len = sr.BaseStream.Length;
                int[] rb = new int[2];

                //skip trailing line terminators
                do
                {
                    len -= 2;
                    sr.BaseStream.Seek(len, SeekOrigin.Begin);
                    rb[1] = sr.BaseStream.ReadByte();
                    rb[0] = sr.BaseStream.ReadByte();
                } while (rb[0] == 10 && rb[1] == 13);

                //go back _lines lines
                int b = 0;
                for (int i = 0; i < Lines - 1; i++)
                {
                    do
                    {
                        len -= 1;
                        try
                        {
                            sr.BaseStream.Seek(len, SeekOrigin.Begin);
                            b = sr.BaseStream.ReadByte();
                        }
                        catch (IOException ex)
                        {
                            Console.WriteLine(ex.Message);

                            //more lines asked for than in file
                            //move to beginning
                            sr.BaseStream.Seek(0, SeekOrigin.Begin);
                        }
                    } while (b != 10 && len > 0);

                    OnReadFileProgress(i, Lines);
                }

                string line = string.Empty;
                do
                {
                    line = sr.ReadLine();

                    if (DoTabs)
                        line = line.Replace(',', '\t');

                    sb.AppendLine(line);
                } while (!String.IsNullOrEmpty(line) && !sr.EndOfStream);
            }

            ReadLines = sb.ToString();
        }

        #region Event Firers

        private void OnReadFileProgress(long progress, long total)
        {
            // if we have subscribers...fire
            if (ReadProgress != null)
            {
                int perc;

                if (total == 0)
                    total = 1;

                if (progress > total)
                    perc = 100;
                else
                    perc = (int) ((decimal) progress / total * 100);

                ReadProgress(this, new ReadProgressEventArgs(perc));
            }
        }

        private void OnFileRead()
        {
            // if we have subscribers...fire
            if (FileRead != null)
                FileRead(this, new EventArgs());
        }

        #endregion
    }

    public delegate void FileReadEventHandler(object sender, EventArgs e);

    public delegate void ReadFileProgressChangedEventHandler(object sender, ReadProgressEventArgs e);

    public class ReadProgressEventArgs : EventArgs
    {
        public int Percentage;

        public ReadProgressEventArgs(int percentage)
        {
            this.Percentage = percentage;
        }
    }

}
